/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with
*  or without modification, are permitted provided that the
*  following conditions are met:
*  Redistributions of source code must retain the above
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef NeuralLayerH
#define NeuralLayerH
#include <NeuralNetwork/Neuron/Neuron.h>
#include <NeuralNetwork/Serialization/INeuralLayerStream.h>
#include <NeuralNetwork/Serialization/NeuralLayerMemento.h>

namespace nn {

/**
 *
 */
template<class Var>
class NeuralLayer {
private:
    /**
     *
     */
    typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > > _neurons;

    /**
     *
     */
    utils::SharedPtr<IEquationFactory<Var> > _equationFactory;

    /**
     *
     */
    unsigned int _inputsNumber;

    /**
     *
     */
    mutable NeuralLayerMemento<Var> _lastMemento;

public:
    /**
     *
     */
    NeuralLayer ( unsigned int inputsNumber, unsigned int neuronsNumber, const typename utils::SharedPtr<IEquationFactory<Var> >& equationFactory ) throw ( NNException ) {
        if ( neuronsNumber == 0 ) {
            throw NNException ( "Wrong argument", __FILE__, __LINE__ );
        }

        for ( unsigned int i = 0; i < neuronsNumber; i++ ) {
            typename utils::SharedPtr<Neuron<Var> > newNeuron( new Neuron<Var > ( inputsNumber, equationFactory ) );
            typename std::pair<unsigned int, utils::SharedPtr<Neuron<Var> > > newEntry ( i, newNeuron );
	    if( !_neurons.insert( newEntry ).second )
	    {
	      throw NNException ( "Memory allocation error", __FILE__, __LINE__ );
	    }
        }

        _equationFactory = equationFactory;
        _inputsNumber = inputsNumber;
    }

    /**
     *
     */
    unsigned int getNeuronsNumber ( void ) const {
        return _neurons.size();
    }

    /**
     *
     */
    unsigned int getInputsNumber() const {
        return _inputsNumber;
    }

    /**
     *
     */
    const utils::SharedPtr< IEquationFactory<Var> >& getEquationFactory() const {
        return _equationFactory;
    }

    /**
     *
     */
    bool setInput ( unsigned int inputId, const Var& value ) {
        typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > >::iterator curNeuron;
        for ( curNeuron = _neurons.begin(); curNeuron != _neurons.end(); curNeuron++ ) {
            if ( !curNeuron->second->setInput ( inputId, value ) ) {
                return false;
            }
        }

        return true;
    }

    /**
     *
     */
    const NeuralLayerMemento<Var>& getMemento() const {
        std::map< unsigned int, NeuronMemento<Var> > neurons;
        typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > >::const_iterator curNeuron;
        for ( curNeuron=_neurons.begin(); curNeuron!=_neurons.end(); curNeuron++ ) {
            std::pair<unsigned int, NeuronMemento<Var> > newEntry ( curNeuron->first, curNeuron->second->getMemento() );
            neurons.insert ( newEntry );
        }

        _lastMemento.setNeurons ( neurons );
        return _lastMemento;
    }

    /**
     *
     */
    bool setMemento ( const NeuralLayerMemento<Var>& memento ) {
        bool result=false;

        if ( memento.getNeuronsNumber() > 0 ) {
            std::map< unsigned int, NeuronMemento<Var> > neurons=memento.getNeurons();

            typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > > internalNeurons;
            typename std::map< unsigned int, NeuronMemento<Var> >::const_iterator curNeuronMemento;
            for ( curNeuronMemento=neurons.begin(); curNeuronMemento!=neurons.end(); curNeuronMemento++ ) {
                utils::SharedPtr< Neuron<Var> > neuron ( new Neuron< Var > ( curNeuronMemento->second.getInputsNumber(), _equationFactory ) );
                internalNeurons.insert ( std::make_pair ( curNeuronMemento->first, neuron ) );
		neuron->setMemento( curNeuronMemento->second );
            }

            _neurons = internalNeurons;
            result = true;
        }

        return result;
    }

    /**
     *
     */
    const typename utils::SharedPtr<Neuron<Var> >& getNeuron ( unsigned int neuronId ) throw ( NNException ) {
        typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > >::iterator curNeuron;
        curNeuron = _neurons.find ( neuronId );
        if ( curNeuron == _neurons.end() ) {
            throw NNException ( "Wrong neuronId", __FILE__, __LINE__ );
        }

        return curNeuron->second;
    }

    /**
     *
     */
    bool calculateLayer ( NeuralLayer<Var>* nextLayer = NULL ) {
        typename std::map<unsigned int, utils::SharedPtr<Neuron<Var> > >::iterator curNeuron;
        for ( curNeuron = _neurons.begin(); curNeuron != _neurons.end(); curNeuron++ ) {
            curNeuron->second->calculateOutput();
            if ( nextLayer != NULL ) {
                if ( !nextLayer->setInput ( curNeuron->first, curNeuron->second->getOutput() ) ) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     *
     */
    virtual ~NeuralLayer() {
    }
};

}

#endif
